package filesystem

import (
	"archive/tar"
	"compress/gzip"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/deploy-services/v2/log"
)

var config string = "service.kcfg"
var DataDir string = "data"
var RecordDir string = ".deploy"
var PublicDir string = "public"
var PackagedDir string = "package"

func SetConfig(file string) {
	config = file
	ioutil.WriteFile(".kcfg", []byte(config), 0666)
}

func Config() string {
	// 1. 先判断本地是否存在配置文件
	// 2. 存在就从本地取配置文件名
	// 3. 不存在就去默认值
	buf, err := ioutil.ReadFile(".kcfg")
	res := config
	if err == nil {
		res = string(buf)
	}
	return res
}

func GetRemotePath(base string, service, file string) string {
	return filepath.ToSlash(filepath.Join(base, service, file))
}

func GetRemoteServicePath(base, service, uuid string) string {
	return filepath.ToSlash(filepath.Join(base, ".commit", service+"."+uuid))
}

func GetRemoteFilePath(base, service, uuid, file string) string {
	return filepath.ToSlash(filepath.Join(GetRemoteServicePath(base, service, uuid), file))
}

func GetRemoteActivePath(base string, service string) string {
	return filepath.ToSlash(filepath.Join(base, service, "active"))
}

func GetServiceInfoPath(service string) string {
	return RecordDir + "/" + service + "/"
}

func GetActiveFilePath(service string) string {
	return GetServiceInfoPath(service) + "active"
}

func GetDependInfoPath(service string, uuid string) string {
	return GetServiceInfoPath(service) + "depends." + uuid
}

func GetHistoryInfoPath(service string) string {
	return GetServiceInfoPath(service) + "history"
}

func GetLocalFilePath(path string) string {
	return filepath.Join(DataDir, path)
}

func GetLocalPublicFilePath(file ...string) string {
	new_arg := []string{RecordDir, PublicDir}
	new_arg = append(new_arg, file...)
	return filepath.Join(new_arg...)
}

func GetLocalDataFilePath(file ...string) string {
	new_arg := []string{DataDir}
	new_arg = append(new_arg, file...)
	return filepath.Join(new_arg...)
}

func GetLocalPackageFilePath(file ...string) string {
	new_arg := []string{PackagedDir}
	new_arg = append(new_arg, file...)
	return filepath.Join(new_arg...)
}

func GetLocalPackagedSourcePath() string {
	return filepath.Join(PackagedDir, "packaged.source")
}

func GetLocalPublicSourcePath(name string) string {
	return filepath.Join(RecordDir, PublicDir, name+".source")
}

func GetLocalPublicDependsPath(name, commit_id string) string {
	return filepath.Join(RecordDir, PublicDir, name+".depends."+commit_id)
}

func GetLocalPublicHistoryPath(name string) string {
	return filepath.Join(RecordDir, PublicDir, name+".history")
}

func GetLocalPublicActivePath(name string) string {
	return filepath.Join(RecordDir, PublicDir, name+".active")
}

func GetRemotePublicPath(base string, file ...string) string {
	new_arg := []string{base, PublicDir}
	new_arg = append(new_arg, file...)
	return filepath.ToSlash(filepath.Join(new_arg...))
}

func GetRemotePublicReleasePath(base string, path string) string {
	path = strings.ReplaceAll(path, "/", ".")
	path = strings.ReplaceAll(path, "\\", ".")
	return filepath.ToSlash(filepath.Join(base, PublicDir, path))
}

func GetRemotePublicCommitPath(base string, path string, file string) string {
	path = strings.ReplaceAll(path, "/", ".")
	path = strings.ReplaceAll(path, "\\", ".")
	return filepath.ToSlash(filepath.Join(base, PublicDir, path+"."+file))
}

func GetRemotePublicActivePath(base, name string) string {
	return filepath.ToSlash(filepath.Join(base, PublicDir, name+".active"))
}

func MkDirs(dir string) error {
	_, err := os.Stat(dir)
	if os.IsNotExist(err) {
		return os.MkdirAll(dir, 0755)
	}
	return nil
}

func RmDirs(dir string) error {
	_, err := os.Stat(dir)
	if os.IsNotExist(err) {
		return nil
	}

	return os.RemoveAll(dir)
}

func RmFile(file string) error {
	_, err := os.Stat(file)
	if os.IsNotExist(err) {
		return nil
	}

	return os.Remove(file)
}

func GetParent(path string) string {
	for i := len(path) - 1; i >= 0; i-- {
		if os.IsPathSeparator(path[i]) {
			return path[0:i]
		}
	}
	return ""
}

func IsExist(file string) bool {
	_, err := os.Stat(file)
	return !os.IsNotExist(err)
}

func CopyDir(src string, dest string) error {
	// 获取源目录信息
	srcInfo, err := os.Stat(src)
	if err != nil {
		return err
	}

	// 创建目标目录
	err = os.MkdirAll(dest, srcInfo.Mode())
	if err != nil {
		return err
	}

	// 读取源目录内容
	entries, err := os.ReadDir(src)
	if err != nil {
		return err
	}

	for _, entry := range entries {
		srcPath := filepath.Join(src, entry.Name())
		destPath := filepath.Join(dest, entry.Name())

		if entry.IsDir() {
			// 递归复制子目录
			err = CopyDir(srcPath, destPath)
			if err != nil {
				return err
			}
		} else {
			// 复制文件
			err = CopyFile(srcPath, destPath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func CopyFile(src, dest string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	// 获取源文件信息以设置权限
	srcInfo, err := os.Stat(src)
	if err != nil {
		return err
	}

	destFile, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcInfo.Mode())
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)
	if err != nil {
		return err
	}

	return nil
}

func TraverveDir(dir string) (*Path, error) {
	p := NewPath()
	ignore_path, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}
	filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
		if err == nil {
			absPath, _ := filepath.Abs(path)
			if len(ignore_path) < len(absPath) {
				p.AddFile(absPath[len(ignore_path)+1:], !d.IsDir())
			}

		}
		return nil
	})
	return p, nil
}

func CaculateMD5(file string) string {
	f, err := os.Open(file)
	if err != nil {
		log.Println("Open", err)
		return ""
	}
	defer f.Close()
	md5hash := md5.New()
	if _, err := io.Copy(md5hash, f); err != nil {
		log.Println("Copy", err)
		return ""
	}
	has := md5hash.Sum(nil)
	return fmt.Sprintf("%x", has)
}

func TarGZ(to, dir string) error {
	tgzFile, err := os.Create(to)
	if err != nil {
		return err
	}
	defer tgzFile.Close()
	gzWriter := gzip.NewWriter(tgzFile)
	defer gzWriter.Close()

	tarWriter := tar.NewWriter(gzWriter)
	defer tarWriter.Close()
	return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		header, err := tar.FileInfoHeader(info, info.Name())
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}
		header.Name = relPath
		if err := tarWriter.WriteHeader(header); err != nil {
			return err
		}

		if !info.Mode().IsRegular() {
			if info.Mode()&os.ModeSymlink != 0 {
				var link string
				if link, err = os.Readlink(path); err != nil {
					log.Println(path, "Is Symlink But Get Target Path Failed.", err)
					return nil
				}
				hdr, err := tar.FileInfoHeader(info, link)
				if err != nil {
					log.Println(path, "Is Symlink But Create Header Failed.", err)
					return nil
				}
				hdr.Name = relPath // 设置正确的路径

				if err := tarWriter.WriteHeader(hdr); err != nil {
					return err
				}
			}
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		_, err = io.Copy(tarWriter, file)
		return err
	})
}

func IsSymlink(path string) (bool, error) {
	fi, err := os.Lstat(path) // 使用 Lstat 而不是 Stat
	if err != nil {
		return false, err
	}

	// 检查文件模式中是否包含软链接标志
	return fi.Mode()&os.ModeSymlink != 0, nil
}

func Tar(to, dir string) error {
	tar_file, err := os.Create(to)
	if err != nil {
		return err
	}

	tarWriter := tar.NewWriter(tar_file)
	defer tarWriter.Close()
	return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		header, err := tar.FileInfoHeader(info, info.Name())
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}
		header.Name = relPath

		if err := tarWriter.WriteHeader(header); err != nil {
			return err
		}

		if !info.Mode().IsRegular() {
			if info.Mode()&os.ModeSymlink != 0 {
				var link string
				if link, err = os.Readlink(path); err != nil {
					log.Println(path, "Is Symlink But Get Target Path Failed.", err)
					return nil
				}
				hdr, err := tar.FileInfoHeader(info, link)
				if err != nil {
					log.Println(path, "Is Symlink But Create Header Failed.", err)
					return nil
				}
				hdr.Name = relPath // 设置正确的路径

				if err := tarWriter.WriteHeader(hdr); err != nil {
					return err
				}
			}
			return nil
		}

		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		_, err = io.Copy(tarWriter, file)
		return err
	})
}

func extractFile(target string, tr *tar.Reader, hdr *tar.Header) error {
	// 确保目录存在
	if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
		return err
	}

	// 创建文件
	outFile, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(hdr.Mode))
	if err != nil {
		return err
	}
	defer outFile.Close()

	// 复制文件内容
	if _, err := io.Copy(outFile, tr); err != nil {
		return err
	}

	return nil
}

func UnpackTarGz(srcPath, destPath string) error {
	// 打开.tar.gz文件
	f, err := os.Open(srcPath)
	if err != nil {
		return err
	}
	defer f.Close()
	// 使用gzip.NewReader来读取gzip数据
	gzReader, err := gzip.NewReader(f)
	if err != nil {
		return err
	}
	defer gzReader.Close()
	// 创建tar.Reader来读取tar数据
	tr := tar.NewReader(gzReader)

	// 循环读取tar文件中的每个文件或目录
	for {
		header, err := tr.Next()
		if err == io.EOF {
			// 文件结束
			break
		}
		if err != nil {
			return err
		}

		// 构建目标文件路径
		targetPath := filepath.Join(destPath, header.Name)
		// 确保目录存在
		switch header.Typeflag {
		case tar.TypeDir:
			os.MkdirAll(targetPath, 0755)
		case tar.TypeReg, tar.TypeRegA:
			if err := extractFile(targetPath, tr, header); err != nil {
				fmt.Printf("解压文件 %s 失败: %v\n", targetPath, err)
			}
		case tar.TypeSymlink:
			// 检查链接目标是否安全
			if runtime.GOOS == "windows" {
				// 检查是否有创建符号链接的权限
				if !hasSymlinkPrivilege() {
					return fmt.Errorf("需要管理员权限来创建符号链接")
				}
			}

			linkTarget := filepath.Join(filepath.Dir(targetPath), header.Linkname)
			if !strings.HasPrefix(linkTarget, filepath.Clean(destPath)+string(os.PathSeparator)) {
				return fmt.Errorf("不安全的符号链接目标: %s -> %s", header.Name, header.Linkname)
			}

			_ = os.Remove(targetPath)
			if err := os.Symlink(header.Linkname, targetPath); err != nil {
				return err
			}
		}
	}
	return nil
}

func UnpackTar(srcPath, destPath string) error {
	// 打开.tar文件
	f, err := os.Open(srcPath)
	if err != nil {
		return err
	}
	defer f.Close()
	// 创建tar.Reader来读取tar数据
	tr := tar.NewReader(f)

	// 循环读取tar文件中的每个文件或目录
	for {
		header, err := tr.Next()
		if err == io.EOF {
			// 文件结束
			break
		}
		if err != nil {
			return err
		}

		// 构建目标文件路径
		targetPath := filepath.Join(destPath, header.Name)
		// 确保目录存在
		switch header.Typeflag {
		case tar.TypeDir:
			os.MkdirAll(targetPath, 0755)
		case tar.TypeReg, tar.TypeRegA:
			if err := extractFile(targetPath, tr, header); err != nil {
				fmt.Printf("解压文件 %s 失败: %v\n", targetPath, err)
			}
		case tar.TypeSymlink:
			// 检查链接目标是否安全
			if runtime.GOOS == "windows" {
				// 检查是否有创建符号链接的权限
				if !hasSymlinkPrivilege() {
					return fmt.Errorf("需要管理员权限来创建符号链接")
				}
			}
			linkTarget := filepath.Join(filepath.Dir(targetPath), header.Linkname)
			if !strings.HasPrefix(linkTarget, filepath.Clean(destPath)+string(os.PathSeparator)) {
				return fmt.Errorf("不安全的符号链接目标: %s -> %s", header.Name, header.Linkname)
			}
			_ = os.Remove(targetPath)
			if err := os.Symlink(header.Linkname, targetPath); err != nil {
				return err
			}
		}
	}
	return nil
}
